// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use log::warn;
use reqwest::Url;

use crate::config::{IndexConfig, PkgIndexEntry};


pub const INDEX_CONFIG_FILE_NAME: &str = "index.toml";

pub struct Mirrors(pub Vec<Mirror>);

pub struct Mirror {
    pub url: Url,
    pub index: IndexConfig,
}

#[derive(Debug, thiserror::Error)]
pub enum FetchIndexConfigError {
    #[error(transparent)]
    InvalidUrl(#[from] url::ParseError),
    #[error(transparent)]
    DownloadError(#[from] reqwest::Error),
    #[error("invalid index config")]
    ParseError(#[from] toml::de::Error),
}

impl Mirrors {
    pub async fn fetch_index_configs<'a, I>(mirror_urls: I) -> Self
    where I: Iterator<Item = &'a str>,
    {
        let mut mirrors = Vec::new();
        for url in mirror_urls {
            match Mirror::fetch_index_config(url).await {
                Ok(mirror) => mirrors.push(mirror),
                Err(err) => warn!("Fetch failed for mirror '{}': {:?}", url, err),
            }
        }
        Self(mirrors)
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub fn contains(&self, spec_str: &str) -> bool {
        self.0.iter().any(|mirror| mirror.contains(spec_str))
    }

    pub fn find(&self, spec_str: &str) -> Option<(&Mirror, &PkgIndexEntry)> {
        self.0.iter().find_map(|mirror| mirror.get(spec_str).map(|c| (mirror, c)))
    }
}

impl Mirror {
    pub async fn fetch_index_config(mirror_url: &str) -> Result<Self, FetchIndexConfigError> {
        let url = Url::parse(&format!("{}/{}", mirror_url, INDEX_CONFIG_FILE_NAME))?;
        let content = reqwest::get(url.as_ref()).await?.text().await?;
        let index = toml::from_str(&content)?;
        Ok(Self { url, index })
    }

    pub fn contains(&self, spec_str: &str) -> bool {
        self.index.contains(spec_str)
    }

    pub fn get(&self, spec_str: &str) -> Option<&PkgIndexEntry> {
        self.index.0.get(spec_str)
    }
}
